TODO:
- Figure out how to store secrets outside of the code ✅
- Copy code from last script ✅
- Convert output to plotly ✅
- Add
xfun::cache_rds to cache tweets and not waste API
calls ✅
- Make tweets available as fly-outs ✅
- Try to load the tweet in a box (crosstalk)
- Sentiment analysis of the replies (crosstalk)
library(rtweet)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
-- Attaching packages ------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5 v purrr 0.3.4
v tibble 3.1.6 v dplyr 1.0.8
v tidyr 1.2.0 v stringr 1.4.0
v readr 2.1.2 v forcats 0.5.1
-- Conflicts ---------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x purrr::flatten() masks rtweet::flatten()
x dplyr::lag() masks stats::lag()
library(keyring)
library(plotly)
Registered S3 method overwritten by 'data.table':
method from
print.data.table
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
Attaching package: ‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
# twitter_token <- create_token(
# app = "pacto-social-1",
# consumer_key =
# keyring::key_get("pacto-social-1-consumer_key"),
# consumer_secret =
# keyring::key_get("pacto-social-1-consumer_secret"),
# access_token =
# keyring::key_get("pacto-social-1-access_token"),
# access_secret =
# keyring::key_get("pacto-social-1-access_secret"),
# set_renv = TRUE)
# Retrieving tweets from Gabriel Boric
tweets_by_boric <-
xfun::cache_rds({
get_timeline("@gabrielboric", n = 3200)
},
file = "tweets_by_boric.rds")
# Retrieving tweets from Jose Antonio Kast
tweets_by_kast <- xfun::cache_rds({
get_timeline("@joseantoniokast", n = 3200)
},
file = "tweets_by_kast.rds")
# Function to filter organic tweets and rank them by engagement
get_organic_ranked <- function(df) {
df %>%
filter(is_retweet == FALSE,
is.na(reply_to_status_id),
# only tweets previous to the presidential election
created_at <= lubridate::ymd(20211220)) %>%
mutate(rank_engagement = dense_rank(desc(favorite_count + retweet_count))) %>%
arrange(rank_engagement)
}
Then I apply the function to both dataframes and merge the results
with map_df. Next, I filter the result to keep only the 10 more popular
tweets of each candidate, and select the relevant columns for the
visualization.
tweets_for_plot <-
list(tweets_by_boric,
tweets_by_kast) %>%
map_df(get_organic_ranked) %>%
filter(rank_engagement <= 10) %>%
transmute(
rank_engagement,
screen_name,
status_id,
created_at,
text,
favorite_count,
retweet_count,
engagement_count = favorite_count + retweet_count,
status_url
)
tweets_for_plot
tweets_for_plot2 <- tweets_for_plot %>%
mutate(engagement_count = ifelse(screen_name == "gabrielboric",
- engagement_count,
engagement_count))
breaks_values_eng <- pretty(tweets_for_plot2$engagement_count)
abs_k <- abs(breaks_values_eng)/1000
labels_k <- ifelse(abs_k == 0, "0", str_c(abs_k, "K"))
fig <- plot_ly(
data = tweets_for_plot2,
x = ~engagement_count,
y = ~rank_engagement,
text = ~abs(engagement_count),
type = 'bar',
color = ~screen_name,
colors = c("red", "blue"),
orientation = 'h',
hovertemplate = paste0(
"<b>%{text:,.0f}</b> likes and RTs | ",
format(tweets_for_plot2$created_at, "%b %d"),
" | @",
tweets_for_plot2$screen_name,
"<br><br>",
str_wrap(tweets_for_plot2$text),
"<extra></extra>"
)
) %>%
layout(barmode = 'overlay',
title = "Most popular tweets by Chilean presidential candidates",
yaxis = list(title = "Ranking",
autorange="reversed",
showgrid=T,
autotick = F, tickmode = "array", tickvals = 1:10),
xaxis = list(title = "Engagement (RTs + Likes)",
tickmode = 'array',
tickvals = breaks_values_eng,
ticktext = labels_k),
legend = list(title=list(text='<b>Candidate</b>'),
orientation = "h", # show entries horizontally
xanchor = "center", # use center of legend as anchor
x = 0.5,
y=-0.2),
hovermode = "closest",
hoverlabel = list(bgcolor = "#DFDFDF", bordercolor = "black")
)
# TODO: make them be in the same vertical position. ✅
# TODO: Fix the "Rank engagement" labels ✅
# TODO: Rename axis ✅
# TODO: Fix the horizontal axis ("Engagement") ✅
# TODO: Clean flyouts (change sign) ✅
# TODO: add twitter text as flyouts ✅
# TODO: change colours ✅
fig
Next
NEXT: crosstalk. https://rstudio.github.io/crosstalk/index.html
Tasks: [✅] Filter tweets after the presidential election [ ]
Retrieve the replies of these tweets Possible approach: - Get all the
followers of each one - Loop through them and get their timelines -
Filter by “in reply to” using the IDs of the tweets I want
LS0tDQp0aXRsZTogIlRvcCBUd2VldHMgZnJvbSBDaGlsZWFuIFByZXNpZGVudGlhbCBDYW5kaWRhdGVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVE9ETzoNCg0KLSAgIEZpZ3VyZSBvdXQgaG93IHRvIHN0b3JlIHNlY3JldHMgb3V0c2lkZSBvZiB0aGUgY29kZSDinIUNCi0gICBDb3B5IGNvZGUgZnJvbSBsYXN0IHNjcmlwdCDinIUNCi0gICBDb252ZXJ0IG91dHB1dCB0byBwbG90bHkg4pyFDQotICAgQWRkIGB4ZnVuOjpjYWNoZV9yZHNgIHRvIGNhY2hlIHR3ZWV0cyBhbmQgbm90IHdhc3RlIEFQSSBjYWxscyDinIUNCi0gICBNYWtlIHR3ZWV0cyBhdmFpbGFibGUgYXMgZmx5LW91dHMg4pyFDQotICAgVHJ5IHRvIGxvYWQgdGhlIHR3ZWV0IGluIGEgYm94IChjcm9zc3RhbGspDQotICAgU2VudGltZW50IGFuYWx5c2lzIG9mIHRoZSByZXBsaWVzIChjcm9zc3RhbGspDQoNCmBgYHtyfQ0KbGlicmFyeShydHdlZXQpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoa2V5cmluZykNCmxpYnJhcnkocGxvdGx5KQ0KDQojIHR3aXR0ZXJfdG9rZW4gPC0gY3JlYXRlX3Rva2VuKA0KIyAgIGFwcCA9ICJwYWN0by1zb2NpYWwtMSIsDQojICAgY29uc3VtZXJfa2V5ID0gDQojICAgICBrZXlyaW5nOjprZXlfZ2V0KCJwYWN0by1zb2NpYWwtMS1jb25zdW1lcl9rZXkiKSwNCiMgICBjb25zdW1lcl9zZWNyZXQgPSANCiMgICAgIGtleXJpbmc6OmtleV9nZXQoInBhY3RvLXNvY2lhbC0xLWNvbnN1bWVyX3NlY3JldCIpLA0KIyAgIGFjY2Vzc190b2tlbiA9IA0KIyAgICAga2V5cmluZzo6a2V5X2dldCgicGFjdG8tc29jaWFsLTEtYWNjZXNzX3Rva2VuIiksDQojICAgYWNjZXNzX3NlY3JldCA9DQojICAgICBrZXlyaW5nOjprZXlfZ2V0KCJwYWN0by1zb2NpYWwtMS1hY2Nlc3Nfc2VjcmV0IiksDQojICAgc2V0X3JlbnYgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyfQ0KIyBSZXRyaWV2aW5nIHR3ZWV0cyBmcm9tIEdhYnJpZWwgQm9yaWMNCnR3ZWV0c19ieV9ib3JpYyA8LQ0KICB4ZnVuOjpjYWNoZV9yZHMoew0KICAgIGdldF90aW1lbGluZSgiQGdhYnJpZWxib3JpYyIsIG4gPSAzMjAwKQ0KICB9LA0KICBmaWxlID0gInR3ZWV0c19ieV9ib3JpYy5yZHMiKQ0KDQojIFJldHJpZXZpbmcgdHdlZXRzIGZyb20gSm9zZSBBbnRvbmlvIEthc3QNCnR3ZWV0c19ieV9rYXN0IDwtIHhmdW46OmNhY2hlX3Jkcyh7DQogIGdldF90aW1lbGluZSgiQGpvc2VhbnRvbmlva2FzdCIsIG4gPSAzMjAwKQ0KICB9LA0KICBmaWxlID0gInR3ZWV0c19ieV9rYXN0LnJkcyIpDQpgYGANCg0KYGBge3J9DQojIEZ1bmN0aW9uIHRvIGZpbHRlciBvcmdhbmljIHR3ZWV0cyBhbmQgcmFuayB0aGVtIGJ5IGVuZ2FnZW1lbnQNCmdldF9vcmdhbmljX3JhbmtlZCA8LSBmdW5jdGlvbihkZikgew0KICBkZiAlPiUNCiAgICBmaWx0ZXIoaXNfcmV0d2VldCA9PSBGQUxTRSwNCiAgICAgICAgICAgaXMubmEocmVwbHlfdG9fc3RhdHVzX2lkKSwNCiAgICAgICAgICAgIyBvbmx5IHR3ZWV0cyBwcmV2aW91cyB0byB0aGUgcHJlc2lkZW50aWFsIGVsZWN0aW9uDQogICAgICAgICAgIGNyZWF0ZWRfYXQgPD0gbHVicmlkYXRlOjp5bWQoMjAyMTEyMjApKSAlPiUNCiAgICBtdXRhdGUocmFua19lbmdhZ2VtZW50ID0gZGVuc2VfcmFuayhkZXNjKGZhdm9yaXRlX2NvdW50ICsgcmV0d2VldF9jb3VudCkpKSAlPiUNCiAgICBhcnJhbmdlKHJhbmtfZW5nYWdlbWVudCkNCn0NCmBgYA0KDQpUaGVuIEkgYXBwbHkgdGhlIGZ1bmN0aW9uIHRvIGJvdGggZGF0YWZyYW1lcyBhbmQgbWVyZ2UgdGhlIHJlc3VsdHMgd2l0aCBtYXBfZGYuIE5leHQsIEkgZmlsdGVyIHRoZSByZXN1bHQgdG8ga2VlcCBvbmx5IHRoZSAxMCBtb3JlIHBvcHVsYXIgdHdlZXRzIG9mIGVhY2ggY2FuZGlkYXRlLCBhbmQgc2VsZWN0IHRoZSByZWxldmFudCBjb2x1bW5zIGZvciB0aGUgdmlzdWFsaXphdGlvbi4NCg0KYGBge3J9DQp0d2VldHNfZm9yX3Bsb3QgPC0NCiAgbGlzdCh0d2VldHNfYnlfYm9yaWMsDQogICAgICAgdHdlZXRzX2J5X2thc3QpICU+JQ0KICBtYXBfZGYoZ2V0X29yZ2FuaWNfcmFua2VkKSAlPiUNCiAgZmlsdGVyKHJhbmtfZW5nYWdlbWVudCA8PSAxMCkgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICByYW5rX2VuZ2FnZW1lbnQsDQogICAgc2NyZWVuX25hbWUsDQogICAgc3RhdHVzX2lkLA0KICAgIGNyZWF0ZWRfYXQsDQogICAgdGV4dCwNCiAgICBmYXZvcml0ZV9jb3VudCwNCiAgICByZXR3ZWV0X2NvdW50LA0KICAgIGVuZ2FnZW1lbnRfY291bnQgPSBmYXZvcml0ZV9jb3VudCArIHJldHdlZXRfY291bnQsDQogICAgc3RhdHVzX3VybA0KICApDQoNCnR3ZWV0c19mb3JfcGxvdA0KYGBgDQoNCmBgYHtyfQ0KdHdlZXRzX2Zvcl9wbG90MiA8LSB0d2VldHNfZm9yX3Bsb3QgJT4lIA0KICBtdXRhdGUoZW5nYWdlbWVudF9jb3VudCA9IGlmZWxzZShzY3JlZW5fbmFtZSA9PSAiZ2FicmllbGJvcmljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBlbmdhZ2VtZW50X2NvdW50LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmdhZ2VtZW50X2NvdW50KSkNCg0KYnJlYWtzX3ZhbHVlc19lbmcgPC0gcHJldHR5KHR3ZWV0c19mb3JfcGxvdDIkZW5nYWdlbWVudF9jb3VudCkNCg0KYWJzX2sgPC0gYWJzKGJyZWFrc192YWx1ZXNfZW5nKS8xMDAwDQoNCmxhYmVsc19rIDwtIGlmZWxzZShhYnNfayA9PSAwLCAiMCIsIHN0cl9jKGFic19rLCAiSyIpKQ0KYGBgDQoNCmBgYHtyfQ0KZmlnIDwtIHBsb3RfbHkoDQogIGRhdGEgPSB0d2VldHNfZm9yX3Bsb3QyLA0KICB4ID0gfmVuZ2FnZW1lbnRfY291bnQsDQogIHkgPSB+cmFua19lbmdhZ2VtZW50LA0KICB0ZXh0ID0gfmFicyhlbmdhZ2VtZW50X2NvdW50KSwNCiAgdHlwZSA9ICdiYXInLA0KICBjb2xvciA9IH5zY3JlZW5fbmFtZSwNCiAgY29sb3JzID0gYygicmVkIiwgImJsdWUiKSwNCiAgb3JpZW50YXRpb24gPSAnaCcsDQogICAgICBob3ZlcnRlbXBsYXRlID0gcGFzdGUwKA0KICAgICAgIjxiPiV7dGV4dDosLjBmfTwvYj4gbGlrZXMgYW5kIFJUcyB8ICIsDQogICAgICBmb3JtYXQodHdlZXRzX2Zvcl9wbG90MiRjcmVhdGVkX2F0LCAiJWIgJWQiKSwNCiAgICAgICIgfCBAIiwNCiAgICAgIHR3ZWV0c19mb3JfcGxvdDIkc2NyZWVuX25hbWUsDQogICAgICAiPGJyPjxicj4iLA0KICAgICAgc3RyX3dyYXAodHdlZXRzX2Zvcl9wbG90MiR0ZXh0KSwNCiAgICAgICI8ZXh0cmE+PC9leHRyYT4iDQogICAgICApDQopICU+JSANCiAgbGF5b3V0KGJhcm1vZGUgPSAnb3ZlcmxheScsDQogICAgICAgICB0aXRsZSA9ICJNb3N0IHBvcHVsYXIgdHdlZXRzIGJ5IENoaWxlYW4gcHJlc2lkZW50aWFsIGNhbmRpZGF0ZXMiLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlJhbmtpbmciLA0KICAgICAgICAgICAgICAgICAgICAgIGF1dG9yYW5nZT0icmV2ZXJzZWQiLA0KICAgICAgICAgICAgICAgICAgICAgIHNob3dncmlkPVQsDQogICAgICAgICAgICAgICAgICAgICAgYXV0b3RpY2sgPSBGLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMToxMCksDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiRW5nYWdlbWVudCAoUlRzICsgTGlrZXMpIiwNCiAgICAgICAgICAgICAgICAgICAgICB0aWNrbW9kZSA9ICdhcnJheScsDQogICAgICAgICAgICAgICAgICAgICAgdGlja3ZhbHMgPSBicmVha3NfdmFsdWVzX2VuZywNCiAgICAgICAgICAgICAgICAgICAgICB0aWNrdGV4dCA9IGxhYmVsc19rKSwNCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGU9bGlzdCh0ZXh0PSc8Yj5DYW5kaWRhdGU8L2I+JyksDQogICAgICAgICAgICAgICAgICAgICAgIG9yaWVudGF0aW9uID0gImgiLCAgICMgc2hvdyBlbnRyaWVzIGhvcml6b250YWxseQ0KICAgICAgICAgICAgICAgICAgICAgICB4YW5jaG9yID0gImNlbnRlciIsICAjIHVzZSBjZW50ZXIgb2YgbGVnZW5kIGFzIGFuY2hvcg0KICAgICAgICAgICAgICAgICAgICAgICB4ID0gMC41LA0KICAgICAgICAgICAgICAgICAgICAgICB5PS0wLjIpLA0KICAgICAgICAgaG92ZXJtb2RlID0gImNsb3Nlc3QiLA0KICAgICAgICAgaG92ZXJsYWJlbCA9IGxpc3QoYmdjb2xvciA9ICIjREZERkRGIiwgYm9yZGVyY29sb3IgPSAiYmxhY2siKQ0KICAgICAgICAgKQ0KDQojIFRPRE86IG1ha2UgdGhlbSBiZSBpbiB0aGUgc2FtZSB2ZXJ0aWNhbCBwb3NpdGlvbi4g4pyFDQojIFRPRE86IEZpeCB0aGUgIlJhbmsgZW5nYWdlbWVudCIgbGFiZWxzIOKchQ0KIyBUT0RPOiBSZW5hbWUgYXhpcyDinIUNCiMgVE9ETzogRml4IHRoZSBob3Jpem9udGFsIGF4aXMgKCJFbmdhZ2VtZW50Iikg4pyFDQojIFRPRE86IENsZWFuIGZseW91dHMgKGNoYW5nZSBzaWduKSDinIUNCiMgVE9ETzogYWRkIHR3aXR0ZXIgdGV4dCBhcyBmbHlvdXRzIOKchSANCiMgVE9ETzogY2hhbmdlIGNvbG91cnMg4pyFDQoNCmZpZw0KYGBgDQoNCiMjIFJldHJpZXZpbmcgcmVwbGllcyB0byB0aGUgdHdlZXRzDQoNCg0KDQojIyBOZXh0DQoNCk5FWFQ6IGNyb3NzdGFsay4NCmh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vY3Jvc3N0YWxrL2luZGV4Lmh0bWwgDQoNClRhc2tzOg0KW+KchV0gRmlsdGVyIHR3ZWV0cyBhZnRlciB0aGUgcHJlc2lkZW50aWFsIGVsZWN0aW9uDQpbIF0gUmV0cmlldmUgdGhlIHJlcGxpZXMgb2YgdGhlc2UgdHdlZXRzDQogICAgUG9zc2libGUgYXBwcm9hY2g6DQogICAgLSBHZXQgYWxsIHRoZSBmb2xsb3dlcnMgb2YgZWFjaCBvbmUNCiAgICAtIExvb3AgdGhyb3VnaCB0aGVtIGFuZCBnZXQgdGhlaXIgdGltZWxpbmVzDQogICAgLSBGaWx0ZXIgYnkgImluIHJlcGx5IHRvIiB1c2luZyB0aGUgSURzIG9mIHRoZSB0d2VldHMgSSB3YW50DQoNCg0K